/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.maven;

import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.internna.iwebjtracker.model.DefaultConnectionDetails;
import org.internna.iwebjtracker.model.DefaultModule;
import org.internna.iwebjtracker.model.DefaultProject;
import org.internna.iwebjtracker.model.DefaultVersion;
import org.internna.iwebjtracker.model.TrackerUser;
import org.internna.iwebjtracker.project.validation.FreeFormProjectTypeValidator;
import org.internna.iwebjtracker.project.model.Project.ProjectType;
import org.internna.iwebjtracker.project.validation.Errors;
import org.internna.iwebjtracker.server.scm.SourceControlManagementServer;
import org.internna.iwebjtracker.server.scm.model.ItemData;
import org.internna.iwebjtracker.server.scm.model.ItemInfo;
import org.internna.iwebjtracker.utils.ConcurrencyUtils;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.springframework.stereotype.Service;

import static org.internna.iwebmvc.utils.StringUtils.hasText;
import static org.internna.iwebmvc.utils.StringUtils.readDocument;
import static org.internna.iwebjtracker.utils.MavenUtils.*;
import static org.internna.iwebjtracker.project.validation.Errors.*;

/**
 * Validator for Maven (pom.xml) project files.
 *
 * @author Jose Noheda
 * @since 1.0
 */
@Service("mavenProjectValidator")
public final class MavenProjectTypeValidator<I extends ItemInfo<DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject>> extends FreeFormProjectTypeValidator<DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> {

    private static final Log logger = LogFactory.getLog(MavenProjectTypeValidator.class);

    /**
     * Delegates the logic to the super class
     */
    public MavenProjectTypeValidator() {
        super(ProjectType.Maven);
    }

    private Document getPom(SourceControlManagementServer<?, DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> server, DefaultProject project, String path) {
        ItemData data = server.loadItem(project, path);
        if ((data != null) && (data.isASCII())) {
            String xml = data.getReadableContent();
            if (hasText(xml)) {
                return readDocument(xml);
            }
        }
        return null;
    }

    /**
     * Fills the project data from pom.xml configuration.
     *
     * @param server a non null server
     * @param project a non null project
     */
    @Override protected Errors preValidate(SourceControlManagementServer<?, DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> server, DefaultProject project) {
        Document pom = getPom(server, project, "/pom.xml");
        if (pom != null) {
            Errors error = validatePOM(project, pom);
            if (error == null) {
                fillHomePage(project, pom);
                fillDescription(project, pom);
                fillModules(server, project, pom);
                if (logger.isDebugEnabled()) {
                    logger.debug("Maven project validation ended successfully: " + project);
                }
            }
            return error;
        }
        return NOT_A_POM;
    }

    private Errors validatePOM(DefaultProject project, Document pom) {
        Errors error = fillName(project, pom);
        if (error == null) {
            error = fillVersion(project, pom);
        }
        return error;
    }

    private Errors fillName(DefaultProject project, Document pom) {
        if (!hasText(project.getProjectName())) {
            project.setProjectName(getName(pom));
        }
        return hasText(project.getProjectName()) ? null : INVALID_POM;
    }

    private Errors fillVersion(DefaultProject project, Document pom) {
        if (project.getCurrentVersion() == null) {
            project.createVersion(getVersion(pom), null);
        }
        return project.getCurrentVersion() == null ? INVALID_POM : null;
    }

    private void fillHomePage(DefaultProject project, Document pom) {
        if (!hasText(project.getHomePage())) {
            project.setHomePage(getUrl(pom));
        }
        if (!hasText(project.getHomePage())) {
            project.setHomePage(project.getSourceControlURL());
        }
    }

    private void fillDescription(DefaultProject project, Document pom) {
        if (!hasText(project.getDescription())) {
            project.setDescription(getDescription(pom));
        }
        if (!hasText(project.getDescription())) {
            project.setDescription(project.getProjectName());
        }
    }

    /**
     * Parses the pom.xml file and retrieves a collection of modules. Access the server
     * to obtain their data (in one new thread per module).
     *
     * @param server a non null instance
     * @param project a non null instance
     * @param pom any
     */
    protected void fillModules(SourceControlManagementServer<?, DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> server, DefaultProject project, Document pom) {
        if (Packaging.POM.equals(getPackaging(pom))) {
            Set<String> modules = getModuleNames(pom);
            if (CollectionUtils.isNotEmpty(modules)) {
                List<ModuleDataRetriever> modulesRetriever = new ArrayList<ModuleDataRetriever>(modules.size());
                for (String module : modules) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Added module [" + module + "] for retrieval in project [" + getName(pom) + "]");
                    }
                    modulesRetriever.add(new ModuleDataRetriever(server, project, module));
                }
                ConcurrencyUtils.parallelExecution(modulesRetriever, true);
                if (logger.isDebugEnabled()) {
                    logger.debug("Module data for project [" + project.getProjectName() + "] retrieved successfully: " + project.getModules());
                }
            }
        }
    }

    private final class ModuleDataRetriever implements Runnable {

        private final String modName;
        private final DefaultProject project;
        private final SourceControlManagementServer<?, DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> server;

        private ModuleDataRetriever(SourceControlManagementServer<?, DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> server, DefaultProject project, String moduleName) {
            this.server = server;
            this.project = project;
            this.modName = moduleName;
        }

        /**
         * Gets module data and adds it to the configured project.
         */
        @Override public void run() {
            long time = System.currentTimeMillis();
            if (logger.isDebugEnabled()) {
                logger.debug("Retrieving module data [" + modName + "] in project [" + project.getProjectName() + "] task started");
            }
            project.addModule(getModuleData(modName, server, null, project, 0));
            if (logger.isDebugEnabled()) {
                logger.debug("Retrieving module data [" + modName + "] in project [" + project.getProjectName() + "] task ended in " + ((System.currentTimeMillis() - time) / 1000) + " seconds");
            }
        }

        private DefaultModule getModuleData(String name, SourceControlManagementServer<?, DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject, I> server, DefaultModule parent, DefaultProject project, int level) {
            Document pom = getPom(server, project, "/" + name + "/pom.xml");
            if (pom != null) {
                DefaultModule module = createModule(pom, name, parent, project);
                if (Packaging.POM.equals(getPackaging(pom)) && (level < 2)) {
                   for (String component : getModuleNames(pom)) {
                        module.addComponent(getModuleData(name + "/" + component, server, module, project, level + 1));
                    }
                }
                return module;
            }
            return null;
        }

        private DefaultModule createModule(Document pom, String name, DefaultModule parent, DefaultProject project) {
            DefaultModule module = new DefaultModule();
            if (parent == null) {
                module.setParentProject(project);
            } else {
                module.setParentModule(parent);
            }
            String moduleName = getName(pom);
            module.setModuleName(hasText(moduleName) ? moduleName : name);
            String description = getDescription(pom);
            module.setDescription(hasText(description) ? description : module.getModuleName());
            return module;
        }

        /**
         * For logging purposes.
         *
         * @return configured module name
         */
        @Override public String toString() {
            return modName;
        }

    }

}
